#include "GRO_utility.h"
#include "GRO_Button.h"
#include "GRO_Checkbox.h"
#include "GRO_Group.h"
#include "GRO_Panel.h"
#include "GRO_Spinner.h"
#include "GRO_Text.h"

using std::string;

namespace GRO
{
    

bool StringConverter::convert(const std::string& str, Rect& rect)
{
    string s = str;
    
    Rect r;
    
    size_t pos = s.find_first_of("[]");
    while(pos != string::npos)
    {
        s.erase(pos, 1);
        pos = s.find_first_of("[]");
    }
    
    pos = s.find_first_of(",");
    if(pos == string::npos)
        return false;
    r.up = atoi(s.substr(0, pos).c_str());
    s.erase(0, pos+1);
    
    pos = s.find_first_of(",");
    if(pos == string::npos)
        return false;
    r.down = atoi(s.substr(0, pos).c_str());
    s.erase(0, pos+1);
    
    pos = s.find_first_of(",");
    if(pos == string::npos)
        return false;
    r.left = atoi(s.substr(0, pos).c_str());
    s.erase(0, pos+1);
    
    r.right = atoi(s.c_str());
    
    rect = r;
    return true;
}

bool StringConverter::convert(const std::string& str, Point& point)
{
    string s = str;
    
    Point r;
    
    size_t pos = s.find_first_of("[]");
    while(pos != string::npos)
    {
        s.erase(pos, 1);
        pos = s.find_first_of("[]");
    }
    
    pos = s.find_first_of(",");
    if(pos == string::npos)
        return false;
    r.x = atoi(s.substr(0, pos).c_str());
    s.erase(0, pos+1);
    
    r.y = atoi(s.c_str());
    
    point = r;
    return true;
}

bool StringConverter::convert(const std::string& str, Dimensions& dims)
{
    string s = str;
    
    Dimensions r;
    
    size_t pos = s.find_first_of("[]");
    while(pos != string::npos)
    {
        s.erase(pos, 1);
        pos = s.find_first_of("[]");
    }
    
    pos = s.find_first_of(",");
    if(pos == string::npos)
        return false;
    r.w = atoi(s.substr(0, pos).c_str());
    s.erase(0, pos+1);
    
    r.h = atoi(s.c_str());
    
    dims = r;
    return true;
}






int toInt(const char* str)
{
    return atoi(str);
}

float toFloat(const char* str)
{
    return atof(str);
}

string xmlGetStringAttr(TiXmlElement* elem, const string& attribute)
{
    const char* attr = elem->Attribute(attribute.c_str());
    if(attr == NULL)
    {
        printf("Failed to load attribute '%s' from '%s' element.\n", attribute.c_str(), elem->Value());
        return "";
    }
    return attr;
}

int xmlGetIntAttr(TiXmlElement* elem, const string& attribute)
{
    const char* attr = elem->Attribute(attribute.c_str());
    if(attr == NULL)
    {
        printf("Failed to load attribute '%s' from '%s' element.\n", attribute.c_str(), elem->Value());
        return 0;
    }
    return toInt(attr);
}

float xmlGetFloatAttr(TiXmlElement* elem, const string& attribute)
{
    const char* attr = elem->Attribute(attribute.c_str());
    if(attr == NULL)
    {
        printf("Failed to load attribute '%s' from '%s' element.\n", attribute.c_str(), elem->Value());
        return 0.0f;
    }
    return toFloat(attr);
}



bool xmlGetRectAttr(TiXmlElement* elem, const string& attribute, Rect& rect)
{
    const char* attr = elem->Attribute(attribute.c_str());
    if(attr == NULL)
    {
        //printf("Failed to load attribute '%s' from '%s' element.\n", attribute.c_str(), elem->Value());
        return false;
    }
    
    return StringConverter::convert(attr, rect);
}

bool xmlGetPointAttr(TiXmlElement* elem, const string& attribute, Point& point)
{
    const char* attr = elem->Attribute(attribute.c_str());
    if(attr == NULL)
    {
        //printf("Failed to load attribute '%s' from '%s' element.\n", attribute.c_str(), elem->Value());
        return false;
    }
    
    return StringConverter::convert(attr, point);
}

bool xmlGetDimsAttr(TiXmlElement* elem, const string& attribute, Dimensions& dims)
{
    const char* attr = elem->Attribute(attribute.c_str());
    if(attr == NULL)
    {
        //printf("Failed to load attribute '%s' from '%s' element.\n", attribute.c_str(), elem->Value());
        return false;
    }
    
    return StringConverter::convert(attr, dims);
}

#define CHECK_LOAD(x) if(x != TIXML_SUCCESS) return false;
#define RETURN_IF_FALSE(x) if(!x) return false;

template<typename T>
T* loadWidget(TiXmlElement* e, Group* group)
{
    T* w = new T;
    w->setGroup(group);
    w->load(e);
    return w;
}

Widget* xmlGetWidget(TiXmlElement* e, Group* group)
{
    std::string s = e->Value();
    if(s == "Button")
        return loadWidget<Button>(e, group);
    else if(s == "Checkbox")
        return loadWidget<Checkbox>(e, group);
    else if(s == "Group")
        return loadWidget<Group>(e, group);
    else if(s == "Panel")
        return loadWidget<Panel>(e, group);
    else if(s == "Spinner")
        return loadWidget<Spinner>(e, group);
    else if(s == "Text")
        return loadWidget<Text>(e, group);
    return NULL;
}









bool Gui::load(const std::string& file)
{
    TiXmlDocument doc;

    if(!doc.LoadFile(file.c_str()))
    {
        //cout << "Failed to load Gui: Couldn't open " << name << "." << endl;
        //cout << doc.ErrorDesc() << endl;
        return false;
    }

    
    TiXmlElement* root = doc.FirstChildElement("GRO_Gui_XML");
    if(root == NULL)
        return false;
    
    
    const char* attr;
    
    // Version
    
    attr = root->Attribute("version");
    if(attr == NULL)
        return false;

    
    TiXmlElement* g = root->FirstChildElement("Gui");
    if(g == NULL)
        return false;
    
    // FIXME: Load Gui attributes.
    // Needs init()?
	if(!xmlGetPointAttr(g, "pos", pos))
        pos = Point(0,0);
	if(!xmlGetDimsAttr(g, "dims", dims))
        dims = Dimensions(0,0);
        
    TiXmlElement* p = g->FirstChildElement("Panel");
    while(p != NULL)
    {
        Panel* panel = new Panel;
        panel->setGui(this);
        panel->load(p);
        add(panel);
        
        p = p->NextSiblingElement("Panel");
    }
    
    return true;
}



// Parses and registers a beacon
void parseBeacon(TiXmlElement* w, Gui* gui, Widget* widget, const std::string& beaconName)
{
    std::string beacon;
    w->QueryStringAttribute(beaconName.c_str(), &beacon);
    while(beacon.size() > 0)
    {
        size_t pos = beacon.find_first_of(";");
        std::string call = beacon.substr(0, pos);
        beacon.erase(0, pos);
        
        if(call.size() > 0)
        {
            // Get name of the widget which is using the function
            size_t pos = call.find_first_of(".");
            std::string fn_widget = call.substr(0, pos);
            call.erase(0, pos+1);
            
            // Get function name
            pos = call.find_first_of("(");
            std::string fn_name = call.substr(0, pos);
            call.erase(0, pos+1);
            
            // FIXME: Parse args string better (I'm assuming no whitespace)
            std::string args = call.substr(0, call.find_first_of(")"));
            //printf("args: %s\n", args.c_str());
            
            std::vector<std::string> argv;
            while(args.size() > 0)
            {
                pos = args.find_first_of(",");
                //printf("pushing arg: %s\n", args.substr(0, pos).c_str());
                argv.push_back(args.substr(0, pos));
                if(pos == std::string::npos)
                    args.erase(0, std::string::npos);
                else
                    args.erase(0, pos+1);
            }
            
            // Parse <##>
            pos = fn_widget.find_first_of("<");
            size_t pos2 = fn_widget.find_first_of(">");
            if(pos != std::string::npos && pos2 != std::string::npos && pos < pos2)
            {
                /*std::string s = fn_widget.substr(pos, pos2 - pos);
                int i = toInt(s.c_str());
                std::map<int, Widget*>::iterator e = idMap.find(i);
                if(e != idMap.end())
                {
                    widget->connectByWidget((*e).second, beaconName.c_str(), fn_name, argv);
                    // FIXME: WON'T WORK!!  I need to store the beacons until later or run through the XML twice.
                    // The widgets need to all be loaded before the beacons can reference them.
                    
                }*/
            }
            else
            {
                // Register with fn_widget name
                if(argv.size() == 0)
                    gui->registerBeacon(widget->name, beaconName.c_str(), fn_widget, fn_name);
                else if(argv.size() == 1)
                    gui->registerBeacon(widget->name, beaconName.c_str(), fn_widget, fn_name, argv[0]);
                else if(argv.size() == 2)
                    gui->registerBeacon(widget->name, beaconName.c_str(), fn_widget, fn_name, argv[0], argv[1]);
            }
        }
    }
}

bool Widget::loadWidgetAttributes(TiXmlElement* w)
{
    // Default: Empty string
	w->QueryStringAttribute("name", &name);
	
	if(!xmlGetPointAttr(w, "pos", pos))
        pos = Point(0,0);
	if(!xmlGetDimsAttr(w, "dims", dims))
        dims = Dimensions(0,0);
	
	if(w->QueryValueAttribute("depth", &_depth) != TIXML_SUCCESS)
        _depth = 128;
	if(w->QueryValueAttribute("hidden", &_hidden) != TIXML_SUCCESS)
        _hidden = false;
	if(w->QueryValueAttribute("folded", &_folded) != TIXML_SUCCESS)
        _folded = false;
	
	if(w->QueryValueAttribute("grabScrollFocus", &grabScrollFocus) != TIXML_SUCCESS)
        grabScrollFocus = false;
	if(w->QueryValueAttribute("grabKeyboardFocus", &grabKeyboardFocus) != TIXML_SUCCESS)
        grabKeyboardFocus = false;
	
	delete designPush;
	designPush = NULL;
	if(w->Attribute("designPush") != NULL)
	{
	    designPush = new Rect;
        if(!xmlGetRectAttr(w, "designPush", *designPush))
        {
            delete designPush;
            designPush = NULL;
            return false;
        }
	}
	
	if(_group != NULL)
        setGui(_group->getGui());
	
	if(_gui != NULL)
	{
	    _gui->registerWidget(this);
	    
        parseBeacon(w, _gui, this, "onPress");
        parseBeacon(w, _gui, this, "onRelease");
        parseBeacon(w, _gui, this, "onClick");
        parseBeacon(w, _gui, this, "onDoubleClick");
        parseBeacon(w, _gui, this, "onScrollUp");
        parseBeacon(w, _gui, this, "onScrollDown");
        parseBeacon(w, _gui, this, "onMotion");
        parseBeacon(w, _gui, this, "onDrag");
        parseBeacon(w, _gui, this, "onHover");
        parseBeacon(w, _gui, this, "onEnter");
        parseBeacon(w, _gui, this, "onLeave");
	}
	
	
    
    return true;
}


bool Button::load(TiXmlElement* w)
{
    loadWidgetAttributes(w);
    
    TiXmlElement* t = w->FirstChildElement("Text");
    text.load(t);
    return true;
}

bool Checkbox::load(TiXmlElement* w)
{
    loadWidgetAttributes(w);
    
    checked = xmlGetIntAttr(w, "checked");
    return true;
}

bool Group::load(TiXmlElement* w)
{
    loadWidgetAttributes(w);
    
    grid.rows = xmlGetIntAttr(w, "gridRows");
    grid.columns = xmlGetIntAttr(w, "gridColumns");
    
    int align = xmlGetIntAttr(w, "alignment");
    switch(align)
    {
        case 0:
            alignment = ALIGN_LEFT_UP;
            break;
        case 1:
            alignment = ALIGN_UP_LEFT;
            break;
        default:
            alignment = ALIGN_LEFT_UP;
    }
    
    xPadding = xmlGetIntAttr(w, "xPadding");
    yPadding = xmlGetIntAttr(w, "yPadding");
    autoFitContents = xmlGetIntAttr(w, "autoFitContents");
    
    RETURN_IF_FALSE(StringConverter::convert(xmlGetStringAttr(w, "displacement"), displacement));
    
    TiXmlElement* g = w->FirstChildElement("Grid");
    
    if(g != NULL)
    {
        TiXmlElement* e = g->FirstChildElement();
        while(e != NULL)
        {
            Widget* w2 = xmlGetWidget(e, this);
            grid.add(w2);
            e = e->NextSiblingElement();
        }
    }
    
    g = w->FirstChildElement("Pins");
    
    if(g != NULL)
    {
        TiXmlElement* e = g->FirstChildElement();
        while(e != NULL)
        {
            Widget* w2 = xmlGetWidget(e, this);
            if(w2 != NULL)
            {
                pin(w2, w2->pos.x, w2->pos.y);
            }
            e = e->NextSiblingElement();
        }
    }
    
    return true;
}

bool Panel::load(TiXmlElement* w)
{
    if(!Group::load(w))
        return false;
        
    grabInput = xmlGetIntAttr(w, "grabInput");
    keepDepth = xmlGetIntAttr(w, "keepDepth");
    
    return true;
}


bool Spinner::load(TiXmlElement* w)
{
    loadWidgetAttributes(w);
    
    value = xmlGetIntAttr(w, "value");
    min = xmlGetIntAttr(w, "min");
    max = xmlGetIntAttr(w, "max");
    increment = xmlGetIntAttr(w, "increment");
    int dir = xmlGetIntAttr(w, "direction");
    switch(dir)
    {
        case 0:
            direction = UP;
            break;
        case 1:
            direction = DOWN;
            break;
        case 2:
            direction = LEFT;
            break;
        case 3:
            direction = RIGHT;
            break;
        default:
            direction = UP;
    }
    
    repeatDelay = xmlGetIntAttr(w, "repeatDelay");
    repeatInitDelay = xmlGetIntAttr(w, "repeatInitDelay");
    
    TiXmlElement* e = w->FirstChildElement("Button");
    if(e != NULL)
        plus.load(e);
    
    e = e->NextSiblingElement("Button");
    if(e != NULL)
        minus.load(e);
    return true;
}


bool Text::load(TiXmlElement* w)
{
    loadWidgetAttributes(w);
    
    text = xmlGetStringAttr(w, "text");
    
    // FIXME: Load buffer
    
    return true;
}











}




